class_name Player
extends CharacterBody2D

signal swap_player_request(player: Player)

enum SkinColor {LIGHT, MEDIUM, DARK}
enum Role {GOALIE, DEFENSE, MIDFIELD, OFFENSE}
enum ControlScheme {CPU, P1, P2}
enum State {CHEST_CONTROL, MOVING, TACKLING, RECOVERING, PREPPING, SHOOTING, PASSING, HEADER, VOLLEY_KICK, BICYCLE_KICK, HURT, DIVING, CELEBRATING, MOURNING, RESETING}
const CONTROL_SCHEME_MAP = {
	ControlScheme.CPU: preload("res://assets/art/props/cpu.png"),
	ControlScheme.P1: preload("res://assets/art/props/1p.png"),
	ControlScheme.P2: preload("res://assets/art/props/2p.png"),
}
const GRAVITY = 8.0
const BALL_CONTROL_HEIGHT_MAX := 10.0
const WALK_ANIM_THRESHOD = 0.6

@export var ball: Ball
@export var control_scheme: ControlScheme
@export var speed: float:
	get:
		return player_data.speed if player_data != null else speed
@export var power: float:
	get:
		return player_data.power if player_data != null else power

@export var own_goal: Goal
@export var target_goal: Goal

@onready var animation_player: AnimationPlayer = %AnimationPlayer
@onready var player_sprite: Sprite2D = %PlayerSprite
@onready var teammate_detection_area: Area2D = %TeammateDetectionArea
@onready var control_sprite: Sprite2D = %ControlSprite
@onready var ball_detection_area: Area2D = %BallDetectionArea
@onready var tackle_damage_emitter_area: Area2D = %TackleDamageEmitterArea
@onready var opponent_detection_area: Area2D = %OpponentDetectionArea
@onready var permit_damage_emitter_area: Area2D = %PermitDamageEmitterArea
@onready var goalie_hand_collider: CollisionShape2D = %GoalieHandCollider
@onready var root_particles: Node2D = %RootParticles
@onready var run_particles: GPUParticles2D = %RunParticles

var kickoff_position := Vector2.ZERO
var spawn_position := Vector2.ZERO
var weight_on_duty_steering := 0.0
var ai_behavior_factory: AIBehaviorFactory = AIBehaviorFactory.new()
var current_ai: AIBehavior = null
var heading := Vector2.RIGHT
var current_state: PlayerState = null
var state_factory := PlayerStateFactory.new()
var height := 0.0
var height_velocity := 0.0
var player_data: PlayerResource
var skin_color: SkinColor:
	get:
		return player_data.skin_color if player_data != null else SkinColor.LIGHT
var role: Role:
	get:
		return player_data.role if player_data != null else Role.OFFENSE
		
var full_name: String:
	get:
		return player_data.full_name if player_data != null else ""
var country: String = ""

func _ready() -> void:
	set_control_texture()
	set_shader_properties()
	setup_ai_behavior()
	permit_damage_emitter_area.monitoring = role == Role.GOALIE
	goalie_hand_collider.disabled = role != Role.GOALIE
	tackle_damage_emitter_area.body_entered.connect(on_tackle_player.bind())
	permit_damage_emitter_area.body_entered.connect(on_tackle_player.bind())
	spawn_position = position
	GameEvent.team_scored_request.connect(on_team_scored.bind())
	GameEvent.team_reset_request.connect(on_team_reset.bind())
	GameEvent.game_over_request.connect(on_game_over.bind())
	var initial_position := kickoff_position if country == GameManager.current_match.country_home else spawn_position
	switch_states(State.RESETING, PlayerStateData.build().set_reset_position(initial_position))
	
func on_tackle_player(other: Player) -> void:
	if other != self and other == ball.carried and other.country != self.country:
		other.get_hurt(position.direction_to(other.position))
	
func setup_ai_behavior() -> void:
	current_ai = ai_behavior_factory.get_ai_behavior(role)
	current_ai.setup(self, ball, opponent_detection_area, teammate_detection_area)
	current_ai.name = "AI Behavior"
	add_child(current_ai)
	
func set_shader_properties() -> void:
	var shader_material := player_sprite.material as ShaderMaterial
	shader_material.set_shader_parameter("skin_color", skin_color)
	var countries := DataLoader.get_countries()
	var country_color := countries.find(country)
	country_color = clampi(country_color, 0, countries.size() - 1)
	shader_material.set_shader_parameter("team_color", country_color)
	
func _process(delta: float) -> void:
	set_sprite_visible()
	flip_sprites()
	process_gravity(delta)
	move_and_slide()
	
func initialize(context_position: Vector2, context_kickoff_position: Vector2, context_ball: Ball, context_own_goal: Goal, context_target_goal: Goal, context_player_data: PlayerResource, context_country: String) -> void:
	position = context_position
	ball = context_ball
	own_goal = context_own_goal
	target_goal = context_target_goal
	player_data = context_player_data
	heading = Vector2.LEFT if target_goal.position.x < position.x else Vector2.RIGHT
	country = context_country
	kickoff_position = context_kickoff_position
	
func switch_states(state: State, state_data: PlayerStateData = PlayerStateData.new()) -> void:
	if current_state != null:
		current_state.queue_free()
		current_state = null
	current_state = state_factory.get_fresh_state(state)
	current_state.setup(self, state_data, animation_player, ball, teammate_detection_area, ball_detection_area, tackle_damage_emitter_area, current_ai)
	current_state.state_register(switch_states.bind())
	current_state.name = "Player State Machine: " + str(state)
	call_deferred("add_child", current_state)
	
func set_control_scheme(context_control_scheme: ControlScheme) -> void:
	control_scheme = context_control_scheme
	set_control_texture()
	
func set_movement_animation() -> void:
	var vel_length = velocity.length()
	
	if vel_length < 1:
		animation_player.play("idle")
	elif vel_length < speed * WALK_ANIM_THRESHOD:
		animation_player.play("walk")
	else:
		animation_player.play("run")
		
func set_heading() -> void:
	if velocity.x > 0:
		heading = Vector2.RIGHT
	elif velocity.x < 0:
		heading = Vector2.LEFT
		
func flip_sprites() -> void:
	if heading == Vector2.RIGHT:
		player_sprite.flip_h = false
		tackle_damage_emitter_area.scale.x = 1
		opponent_detection_area.scale.x = 1
		root_particles.scale.x = 1
	elif heading == Vector2.LEFT:
		player_sprite.flip_h = true
		tackle_damage_emitter_area.scale.x = -1
		opponent_detection_area.scale.x = -1
		root_particles.scale.x = -1
		
func has_ball() -> bool:
	return ball.carried == self
	
func on_animation_complete() -> void:
	if current_state != null:
		current_state.on_animation_complete()
		
func is_cpu() -> bool:
	return control_scheme == ControlScheme.CPU
	
func set_control_texture() -> void:
	if control_sprite == null:
		return
	control_sprite.texture = CONTROL_SCHEME_MAP[control_scheme]
	
func set_sprite_visible() -> void:
	control_sprite.visible = true if has_ball() or not is_cpu() else false
	run_particles.emitting = velocity.length() == speed
	
func process_gravity(delta: float) -> void:
	if height > 0:
		height_velocity -= GRAVITY * delta
		height += height_velocity
		if height < 0:
			height = 0
	player_sprite.position = Vector2.UP * height
	
func is_face_target_goal() -> bool:
	var direction_to_target_goal := position.direction_to(target_goal.position)
	return heading.dot(direction_to_target_goal) > 0
	
func get_target_position() -> Vector2:
	return target_goal.get_random_target_position()
	
func control_ball() -> void:
	if ball.height > BALL_CONTROL_HEIGHT_MAX:
		switch_states(State.CHEST_CONTROL)
		
func get_hurt(hurt_origin: Vector2) -> void:
	switch_states(State.HURT, PlayerStateData.build().set_hurt_direction(hurt_origin))
	
func can_carry_ball() -> bool:
	return current_state != null and current_state.can_carry_ball()

func get_pass_request(player: Player) -> void:
	if ball.carried == self and current_state != null and current_state.can_pass_ball():
		switch_states(Player.State.PASSING, PlayerStateData.build().set_pass_target(player))
		
func swap_player(player: Player) -> void:
	swap_player_request.emit(player)
	
func on_team_scored(team_scored_on: String) -> void:
	if country == team_scored_on:
		switch_states(State.MOURNING)
	else:
		switch_states(State.CELEBRATING)
		
func on_game_over(winning_team: String) -> void:
	if country == winning_team:
		switch_states(State.CELEBRATING)
	else:
		switch_states(State.MOURNING)
	
func on_team_reset() -> void:
	set_control_scheme(ControlScheme.CPU)

func face_towards_target_goal() -> void:
	if not is_face_target_goal():
		heading *= -1
		
func is_ready_for_kickoff() -> bool:
	return current_state != null and current_state.is_ready_for_kickoff()
